1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package sun.java2d.pisces;
27
28 import sun.awt.geom.PathConsumer2D;
29
30
31
32
33
34
35
36
37
38
39
40
41 final class Dasher implements sun.awt.geom.PathConsumer2D {
42
43 private final PathConsumer2D out;
44 private final float[] dash;
45 private final float startPhase;
46 private final boolean startDashOn;
47 private final int startIdx;
48
49 private boolean starting;
50 private boolean needsMoveTo;
51
52 private int idx;
53 private boolean dashOn;
54 private float phase;
55
56 private float sx, sy;
57 private float x0, y0;
58
59
60 private float[] curCurvepts;
61
62
63
64
65
66
67
68
69 public Dasher(PathConsumer2D out, float[] dash, float phase) {
70 if (phase < 0) {
71 throw new IllegalArgumentException("phase < 0 !");
72 }
73
74 this.out = out;
75
76
77 int idx = 0;
78 dashOn = true;
79 float d;
80 while (phase >= (d = dash[idx])) {
81 phase -= d;
82 idx = (idx + 1) % dash.length;
83 dashOn = !dashOn;
84 }
85
86 this.dash = dash;
87 this.startPhase = this.phase = phase;
88 this.startDashOn = dashOn;
89 this.startIdx = idx;
90 this.starting = true;
91
92
93
94 curCurvepts = new float[8 * 2];
95 }
96
97 public void moveTo(float x0, float y0) {
98 if (firstSegidx > 0) {
99 out.moveTo(sx, sy);
100 emitFirstSegments();
101 }
102 needsMoveTo = true;
103 this.idx = startIdx;
104 this.dashOn = this.startDashOn;
105 this.phase = this.startPhase;
106 this.sx = this.x0 = x0;
107 this.sy = this.y0 = y0;
108 this.starting = true;
109 }
110
111 private void emitSeg(float[] buf, int off, int type) {
112 switch (type) {
113 case 8:
114 out.curveTo(buf[off+0], buf[off+1],
115 buf[off+2], buf[off+3],
116 buf[off+4], buf[off+5]);
117 break;
118 case 6:
119 out.quadTo(buf[off+0], buf[off+1],
120 buf[off+2], buf[off+3]);
121 break;
122 case 4:
123 out.lineTo(buf[off], buf[off+1]);
124 }
125 }
126
127 private void emitFirstSegments() {
128 for (int i = 0; i < firstSegidx; ) {
129 emitSeg(firstSegmentsBuffer, i+1, (int)firstSegmentsBuffer[i]);
130 i += (((int)firstSegmentsBuffer[i]) - 1);
131 }
132 firstSegidx = 0;
133 }
134
135
136
137
138
139 private float[] firstSegmentsBuffer = new float[7];
140 private int firstSegidx = 0;
141
142
143 private void goTo(float[] pts, int off, final int type) {
144 float x = pts[off + type - 4];
145 float y = pts[off + type - 3];
146 if (dashOn) {
147 if (starting) {
148 firstSegmentsBuffer = Helpers.widenArray(firstSegmentsBuffer,
149 firstSegidx, type - 2);
150 firstSegmentsBuffer[firstSegidx++] = type;
151 System.arraycopy(pts, off, firstSegmentsBuffer, firstSegidx, type - 2);
152 firstSegidx += type - 2;
153 } else {
154 if (needsMoveTo) {
155 out.moveTo(x0, y0);
156 needsMoveTo = false;
157 }
158 emitSeg(pts, off, type);
159 }
160 } else {
161 starting = false;
162 needsMoveTo = true;
163 }
164 this.x0 = x;
165 this.y0 = y;
166 }
167
168 public void lineTo(float x1, float y1) {
169 float dx = x1 - x0;
170 float dy = y1 - y0;
171
172 float len = (float) Math.sqrt(dx*dx + dy*dy);
173
174 if (len == 0) {
175 return;
176 }
177
178
179
180 float cx = dx / len;
181 float cy = dy / len;
182
183 while (true) {
184 float leftInThisDashSegment = dash[idx] - phase;
185 if (len <= leftInThisDashSegment) {
186 curCurvepts[0] = x1;
187 curCurvepts[1] = y1;
188 goTo(curCurvepts, 0, 4);
189
190 phase += len;
191 if (len == leftInThisDashSegment) {
192 phase = 0f;
193 idx = (idx + 1) % dash.length;
194 dashOn = !dashOn;
195 }
196 return;
197 }
198
199 float dashdx = dash[idx] * cx;
200 float dashdy = dash[idx] * cy;
201 if (phase == 0) {
202 curCurvepts[0] = x0 + dashdx;
203 curCurvepts[1] = y0 + dashdy;
204 } else {
205 float p = leftInThisDashSegment / dash[idx];
206 curCurvepts[0] = x0 + p * dashdx;
207 curCurvepts[1] = y0 + p * dashdy;
208 }
209
210 goTo(curCurvepts, 0, 4);
211
212 len -= leftInThisDashSegment;
213
214 idx = (idx + 1) % dash.length;
215 dashOn = !dashOn;
216 phase = 0;
217 }
218 }
219
220 private LengthIterator li = null;
221
222
223
224 private void somethingTo(int type) {
225 if (pointCurve(curCurvepts, type)) {
226 return;
227 }
228 if (li == null) {
229 li = new LengthIterator(4, 0.01f);
230 }
231 li.initializeIterationOnCurve(curCurvepts, type);
232
233 int curCurveoff = 0;
234 float lastSplitT = 0;
235 float t = 0;
236 float leftInThisDashSegment = dash[idx] - phase;
237 while ((t = li.next(leftInThisDashSegment)) < 1) {
238 if (t != 0) {
239 Helpers.subdivideAt((t - lastSplitT) / (1 - lastSplitT),
240 curCurvepts, curCurveoff,
241 curCurvepts, 0,
242 curCurvepts, type, type);
243 lastSplitT = t;
244 goTo(curCurvepts, 2, type);
245 curCurveoff = type;
246 }
247
248 idx = (idx + 1) % dash.length;
249 dashOn = !dashOn;
250 phase = 0;
251 leftInThisDashSegment = dash[idx];
252 }
253 goTo(curCurvepts, curCurveoff+2, type);
254 phase += li.lastSegLen();
255 if (phase >= dash[idx]) {
256 phase = 0f;
257 idx = (idx + 1) % dash.length;
258 dashOn = !dashOn;
259 }
260 }
261
262 private static boolean pointCurve(float[] curve, int type) {
263 for (int i = 2; i < type; i++) {
264 if (curve[i] != curve[i-2]) {
265 return false;
266 }
267 }
268 return true;
269 }
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284 private static class LengthIterator {
285 private enum Side {LEFT, RIGHT};
286
287
288
289
290 private float[][] recCurveStack;
291
292
293 private Side[] sides;
294 private int curveType;
295 private final int limit;
296 private final float ERR;
297 private final float minTincrement;
298
299 private float nextT;
300 private float lenAtNextT;
301 private float lastT;
302 private float lenAtLastT;
303 private float lenAtLastSplit;
304 private float lastSegLen;
305
306
307 private int recLevel;
308 private boolean done;
309
310
311
312
313 private float[] curLeafCtrlPolyLengths = new float[3];
314
315 public LengthIterator(int reclimit, float err) {
316 this.limit = reclimit;
317 this.minTincrement = 1f / (1 << limit);
318 this.ERR = err;
319 this.recCurveStack = new float[reclimit+1][8];
320 this.sides = new Side[reclimit];
321
322
323 this.nextT = Float.MAX_VALUE;
324 this.lenAtNextT = Float.MAX_VALUE;
325 this.lenAtLastSplit = Float.MIN_VALUE;
326 this.recLevel = Integer.MIN_VALUE;
327 this.lastSegLen = Float.MAX_VALUE;
328 this.done = true;
329 }
330
331 public void initializeIterationOnCurve(float[] pts, int type) {
332 System.arraycopy(pts, 0, recCurveStack[0], 0, type);
333 this.curveType = type;
334 this.recLevel = 0;
335 this.lastT = 0;
336 this.lenAtLastT = 0;
337 this.nextT = 0;
338 this.lenAtNextT = 0;
339 goLeft();
340 this.lenAtLastSplit = 0;
341 if (recLevel > 0) {
342 this.sides[0] = Side.LEFT;
343 this.done = false;
344 } else {
345
346 this.sides[0] = Side.RIGHT;
347 this.done = true;
348 }
349 this.lastSegLen = 0;
350 }
351
352
353 private int cachedHaveLowAcceleration = -1;
354
355 private boolean haveLowAcceleration(float err) {
356 if (cachedHaveLowAcceleration == -1) {
357 final float len1 = curLeafCtrlPolyLengths[0];
358 final float len2 = curLeafCtrlPolyLengths[1];
359
360
361
362 if (!Helpers.within(len1, len2, err*len2)) {
363 cachedHaveLowAcceleration = 0;
364 return false;
365 }
366 if (curveType == 8) {
367 final float len3 = curLeafCtrlPolyLengths[2];
368
369
370
371 if (!(Helpers.within(len2, len3, err*len3) &&
372 Helpers.within(len1, len3, err*len3))) {
373 cachedHaveLowAcceleration = 0;
374 return false;
375 }
376 }
377 cachedHaveLowAcceleration = 1;
378 return true;
379 }
380
381 return (cachedHaveLowAcceleration == 1);
382 }
383
384
385
386 private float[] nextRoots = new float[4];
387
388
389
390
391
392 private float[] flatLeafCoefCache = new float[] {0, 0, -1, 0};
393
394
395
396 public float next(final float len) {
397 final float targetLength = lenAtLastSplit + len;
398 while(lenAtNextT < targetLength) {
399 if (done) {
400 lastSegLen = lenAtNextT - lenAtLastSplit;
401 return 1;
402 }
403 goToNextLeaf();
404 }
405 lenAtLastSplit = targetLength;
406 final float leaflen = lenAtNextT - lenAtLastT;
407 float t = (targetLength - lenAtLastT) / leaflen;
408
409
410
411 if (!haveLowAcceleration(0.05f)) {
412
413
414
415
416
417 if (flatLeafCoefCache[2] < 0) {
418 float x = 0+curLeafCtrlPolyLengths[0],
419 y = x+curLeafCtrlPolyLengths[1];
420 if (curveType == 8) {
421 float z = y + curLeafCtrlPolyLengths[2];
422 flatLeafCoefCache[0] = 3*(x - y) + z;
423 flatLeafCoefCache[1] = 3*(y - 2*x);
424 flatLeafCoefCache[2] = 3*x;
425 flatLeafCoefCache[3] = -z;
426 } else if (curveType == 6) {
427 flatLeafCoefCache[0] = 0f;
428 flatLeafCoefCache[1] = y - 2*x;
429 flatLeafCoefCache[2] = 2*x;
430 flatLeafCoefCache[3] = -y;
431 }
432 }
433 float a = flatLeafCoefCache[0];
434 float b = flatLeafCoefCache[1];
435 float c = flatLeafCoefCache[2];
436 float d = t*flatLeafCoefCache[3];
437
438
439
440
441 int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0, 1);
442 if (n == 1 && !Float.isNaN(nextRoots[0])) {
443 t = nextRoots[0];
444 }
445 }
446
447
448 t = t * (nextT - lastT) + lastT;
449 if (t >= 1) {
450 t = 1;
451 done = true;
452 }
453
454
455
456
457
458 lastSegLen = len;
459 return t;
460 }
461
462 public float lastSegLen() {
463 return lastSegLen;
464 }
465
466
467
468 private void goToNextLeaf() {
469
470
471 recLevel--;
472 while(sides[recLevel] == Side.RIGHT) {
473 if (recLevel == 0) {
474 done = true;
475 return;
476 }
477 recLevel--;
478 }
479
480 sides[recLevel] = Side.RIGHT;
481 System.arraycopy(recCurveStack[recLevel], 0, recCurveStack[recLevel+1], 0, curveType);
482 recLevel++;
483 goLeft();
484 }
485
486
487 private void goLeft() {
488 float len = onLeaf();
489 if (len >= 0) {
490 lastT = nextT;
491 lenAtLastT = lenAtNextT;
492 nextT += (1 << (limit - recLevel)) * minTincrement;
493 lenAtNextT += len;
494
495 flatLeafCoefCache[2] = -1;
496 cachedHaveLowAcceleration = -1;
497 } else {
498 Helpers.subdivide(recCurveStack[recLevel], 0,
499 recCurveStack[recLevel+1], 0,
500 recCurveStack[recLevel], 0, curveType);
501 sides[recLevel] = Side.LEFT;
502 recLevel++;
503 goLeft();
504 }
505 }
506
507
508
509 private float onLeaf() {
510 float[] curve = recCurveStack[recLevel];
511 float polyLen = 0;
512
513 float x0 = curve[0], y0 = curve[1];
514 for (int i = 2; i < curveType; i += 2) {
515 final float x1 = curve[i], y1 = curve[i+1];
516 final float len = Helpers.linelen(x0, y0, x1, y1);
517 polyLen += len;
518 curLeafCtrlPolyLengths[i/2 - 1] = len;
519 x0 = x1;
520 y0 = y1;
521 }
522
523 final float lineLen = Helpers.linelen(curve[0], curve[1], curve[curveType-2], curve[curveType-1]);
524 if (polyLen - lineLen < ERR || recLevel == limit) {
525 return (polyLen + lineLen)/2;
526 }
527 return -1;
528 }
529 }
530
531 @Override
532 public void curveTo(float x1, float y1,
533 float x2, float y2,
534 float x3, float y3)
535 {
536 curCurvepts[0] = x0; curCurvepts[1] = y0;
537 curCurvepts[2] = x1; curCurvepts[3] = y1;
538 curCurvepts[4] = x2; curCurvepts[5] = y2;
539 curCurvepts[6] = x3; curCurvepts[7] = y3;
540 somethingTo(8);
541 }
542
543 @Override
544 public void quadTo(float x1, float y1, float x2, float y2) {
545 curCurvepts[0] = x0; curCurvepts[1] = y0;
546 curCurvepts[2] = x1; curCurvepts[3] = y1;
547 curCurvepts[4] = x2; curCurvepts[5] = y2;
548 somethingTo(6);
549 }
550
551 public void closePath() {
552 lineTo(sx, sy);
553 if (firstSegidx > 0) {
554 if (!dashOn || needsMoveTo) {
555 out.moveTo(sx, sy);
556 }
557 emitFirstSegments();
558 }
559 moveTo(sx, sy);
560 }
561
562 public void pathDone() {
563 if (firstSegidx > 0) {
564 out.moveTo(sx, sy);
565 emitFirstSegments();
566 }
567 out.pathDone();
568 }
569
570 @Override
571 public long getNativeConsumer() {
572 throw new InternalError("Dasher does not use a native consumer");
573 }
574 }
575